package cn.sylinx.horm.proxy.command;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import cn.sylinx.horm.core.DynamicClient;
import cn.sylinx.horm.core.datasource.dynamic.DynamicDatasourceProvider;
import cn.sylinx.horm.exception.HORMException;
import cn.sylinx.horm.proxy.annotation.PageNumber;
import cn.sylinx.horm.proxy.annotation.PageSize;
import cn.sylinx.horm.proxy.annotation.Param;
import cn.sylinx.horm.proxy.annotation.ParamBean;
import cn.sylinx.horm.proxy.command.annotation.Command;
import cn.sylinx.horm.proxy.command.annotation.Datasource;
import cn.sylinx.horm.proxy.command.annotation.Resource;
import cn.sylinx.horm.util.BeanUtil;
import cn.sylinx.horm.util.Pair;
import cn.sylinx.horm.util.StrKit;
import cn.sylinx.horm.util.Tuple;

public class CommandMethodMetadata {

    private static final Map<String, CommandMethodMetadata> cachedMethod = new HashMap<>();
    private final Class<?> commandInterface;
    private final Method method;

    private Command commandAnnotation;
    private Class<?> truelyReturnType;
    private Annotation[][] parameterAnnotations;
    private Resource resource;
    private String truelyDatasource;
    private Class<?> returnType;
    // 是否用native sql
    private boolean nativeSql = false;
    private String metaKey;

    private static String getMetaKey(Class<?> commandInterface, Method method) {
        return commandInterface.getName() + "." + method.toString();
    }

    public static CommandMethodMetadata get(Class<?> commandInterface, Method method) {
        String key = getMetaKey(commandInterface, method);
        CommandMethodMetadata methodMetadata = get(key);
        if (methodMetadata == null) {
            methodMetadata = setAndGet(key, commandInterface, method);
        }
        return methodMetadata;
    }

    private static CommandMethodMetadata get(String key) {
        return cachedMethod.get(key);
    }

    private static CommandMethodMetadata setAndGet(String key, Class<?> commandInterface, Method method) {
        synchronized (key.intern()) {
            CommandMethodMetadata methodMetadata = get(key);
            if (methodMetadata != null) {
                return methodMetadata;
            }
            methodMetadata = new CommandMethodMetadata(commandInterface, method);
            cachedMethod.put(key, methodMetadata);
            return methodMetadata;
        }
    }

    public CommandMethodMetadata(Class<?> commandInterface, Method method) {
        this.commandInterface = commandInterface;
        this.method = method;
        readMetadata();
    }

    private void readMetadata() {
        // 类全局注解
        this.commandAnnotation = readCommandAnnotation();
        // 返回类型
        this.returnType = readReturnType();
        // 获取返回参数
        this.truelyReturnType = readTruelyReturnType();
        // 参数的所有注解
        this.parameterAnnotations = readParameterAnnotations();
        // 注解
        this.resource = readAndCheckResource();
        // 全局数据源
        this.truelyDatasource = readTruelyDatasource();
        // native sql
        this.nativeSql = this.resource.nativeSql() != null && !this.resource.nativeSql().trim().isEmpty();
        // 元数据唯一标识
        this.metaKey = getMetaKey(commandInterface, method);
    }

    private Class<?> readReturnType() {
        return method.getReturnType();
    }

    private Command readCommandAnnotation() {
        return commandInterface.getAnnotation(Command.class);
    }

    private Annotation[][] readParameterAnnotations() {
        return method.getParameterAnnotations();
    }

    private Resource readAndCheckResource() {
        Resource resource = method.getAnnotation(Resource.class);
        if (resource == null) {
            throw new HORMException("sql resource is empty");
        }
        String sqlResource = resource.sql();
        String nativeSql = resource.nativeSql();
        boolean isSqlResourceNull = sqlResource == null || sqlResource.trim().isEmpty();
        boolean isNativeSqlNull = nativeSql == null || nativeSql.trim().isEmpty();
        if (isSqlResourceNull && isNativeSqlNull) {
            throw new HORMException("SQL资源和本地SQL不能都为空");
        }
        return resource;
    }

    /**
     * 解析参数
     * 
     * @param args 参数
     * @return Tuple[0]:查询参数, Tuple[1]:pageNumber, Tuple[2]:pageSize,
     *         Tuple[3]:datasource
     */
    @SuppressWarnings("unchecked")
    public Tuple resove(Object[] args) {
        Integer pageNumber = 0, pageSize = 0;
        // 所有Param参数列表
        List<Pair> paramList = new ArrayList<>();
        // 所有map
        List<Map<String, Object>> mapList = new ArrayList<>();
        // 所有对象类型
        List<Object> beanList = new ArrayList<>();
        // 数据源判断
        List<String> datasourceList = new ArrayList<>();
        for (int i = 0; i < parameterAnnotations.length; ++i) {
            Annotation[] ats = parameterAnnotations[i];
            if (ats != null && ats.length > 0) {
                if (ats[0].annotationType() == PageNumber.class) {
                    pageNumber = (Integer) args[i];
                }
                if (ats[0].annotationType() == PageSize.class) {
                    pageSize = (Integer) args[i];
                }
                if (ats[0].annotationType() == Param.class) {
                    paramList.add(Pair.apply(ats[0], args[i]));
                }
                if (ats[0].annotationType() == ParamBean.class) {
                    beanList.add(args[i]);
                }
                if (ats[0].annotationType() == Datasource.class) {
                    Object dsObject = args[i];
                    if (dsObject != null) {
                        if (dsObject.getClass() != String.class) {
                            // 非字符串类型
                            throw new HORMException("数据源类型错误");
                        }
                        datasourceList.add(dsObject.toString());
                    }
                }
            }
            if (args[i] != null && Map.class.isAssignableFrom(args[i].getClass())) {
                mapList.add((Map<String, Object>) args[i]);
            }
        }
        if (datasourceList.size() > 1) {
            throw new HORMException("不能配置多个数据源");
        }
        String datasource = null;
        if (!datasourceList.isEmpty()) {
            datasource = datasourceList.get(0);
        }
        if (datasource == null) {
            datasource = truelyDatasource;
        }
        Map<String, Object> ps = new HashMap<>();
        // 遍历Param参数
        paramList.forEach(p -> {
            Param param = p.getObject(0);
            ps.put(param.value(), p.getObject(1));
        });
        // 遍历所有map
        mapList.forEach(ps::putAll);
        // 遍历所有bean
        beanList.forEach(bean -> ps.putAll(BeanUtil.bean2map(bean)));
        // 如果pageNumber,pageSize 为空，则从参数中获取
        if (pageNumber == null || pageNumber < 1) {
            pageNumber = Optional.ofNullable((Integer) ps.get("pageNumber")).orElse(0);
            pageSize = Optional.ofNullable((Integer) ps.get("pageSize")).orElse(0);
        }
        if (commandAnnotation.dynamic()) {
            // 如果是动态数据源，则切换
            datasource = DynamicDatasourceProvider.create().getDatasource();
        }
        return Tuple.apply(ps, pageNumber, pageSize, datasource);
    }

    private String readTruelyDatasource() {
        String datasourcePre = commandAnnotation.datasource().trim();
        String truelyDatasource = StrKit.isEmpty(datasourcePre) ? null : datasourcePre;
        String datasource = resource.datasource().trim();
        if (!datasource.isEmpty()) {
            truelyDatasource = datasource;
        }
        if (StrKit.isBlank(truelyDatasource)) {
            truelyDatasource = DynamicClient.DEFAULT_DS_NAME;
        }
        return truelyDatasource;
    }

    private Class<?> readTruelyReturnType() {
        Type genericReturnType = method.getGenericReturnType();
        // 获取返回值的泛型参数
        if (genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            return (Class<?>) actualTypeArguments[0];
        }
        return Object.class;
    }

    public Class<?> getTruelyReturnType() {
        return truelyReturnType;
    }

    public Resource getResource() {
        return resource;
    }

    public Class<?> getCommandInterface() {
        return commandInterface;
    }

    public Command getCommandAnnotation() {
        return commandAnnotation;
    }

    public Method getMethod() {
        return method;
    }

    public Class<?> getReturnType() {
        return returnType;
    }

    public boolean isNativeSql() {
        return nativeSql;
    }

    public String getMetaKey() {
        return metaKey;
    }

    public void setMetaKey(String metaKey) {
        this.metaKey = metaKey;
    }
}